#include "stdafx.h"
#include "Scene.h"
#include "..\Render\Render.h"
#include "..\..\Global\globaldef.h"
#include <stdio.h>

#pragma message "refactor!"
extern bool CharBuffer_Equals(const tCharBuffer* pBuuffer, const char* pString);

std::vector<Model*> Scene::g_Geometry = std::vector<Model*> ();
std::vector<tLight*> Scene::g_Light = std::vector<tLight*> ();

/**
	Initalized the scene instance

	@author Dacre Denny
	@return true if succesful otherwise false
*/
bool Scene::Create() {

	return true;
}

/**
	Releases the scene instance and all resource that it internally owns

	@author Dacre Denny
	@return true if successful otherwise false
*/
bool Scene::Release() {

	FOREACH(Model*, i, Scene::g_Geometry) {

		Model* pMesh = (*i);
		if(pMesh) {

			pMesh->Release();
			delete pMesh;
		}
	}
	Scene::g_Geometry.clear();

	return true;
}

/**
	Returns the geometry object repsective of the specified name. The function
	fails if either of the parameters are invalid

	@author Dacre Denny
	@param pointer to address of resulting object if it is found
	@param pointer to identification string/object
	@return true if successful and parameters are valid otherwise false
*/
bool Scene::GetGeometry(Model **pOut, tCharBuffer *pName) {

	if(!pName || !pOut)
		return false;

	FOREACH(Model*, i, Scene::g_Geometry) {

		Model* pMesh = (*i);
		if(pMesh) {

			tCharBuffer meshID;
			pMesh->GetID(&meshID);
			if(strncmp(pName->Buffer, meshID.Buffer, sizeof(meshID.Buffer)) == 0) {

				*pOut = pMesh;
				return true;
			}
		}
	}

	return false;
}

/**
	Enables geometry to be inserted into the scene instance. The function
	fails if the parameter is invalid. If a name conflict is detected the
	specified mesh will replace the existing mesh

	@author Dacre Denny
	@param pointer to the geometry object to be inserted
	@return true if successful and parameter is valid otherwise false
*/
bool Scene::InsertGeometry(Model *pIn) {

	if(!pIn)
		return false;

	Model* pCached = NULL;
	tCharBuffer modelID;
	pIn->GetID(&modelID);
	if(GetGeometry(&pCached, &modelID)) {

		if(!Scene::RemoveGeometry(pCached))
			return false;
	}

	Scene::g_Geometry.push_back(pIn);
	return true;
}

/**
	Enables geometry to be removed from the scene instance. The function
	fails if the parameter is invalid. 

	@author Dacre Denny
	@param pointer to the geometry object to be removed
	@return true if successful and parameter is valid otherwise false
*/
bool Scene::RemoveGeometry(Model *pIn) {

	if(!pIn)
		return false;

	tCharBuffer inID;
	pIn->GetID(&inID);

	FOREACH(Model*, i, Scene::g_Geometry) {

		Model* pMesh = (*i);
		if(pMesh) {

			tCharBuffer modelID;
			pMesh->GetID(&modelID);
			if(strncmp(inID.Buffer, modelID.Buffer, sizeof(tCharBuffer)) == 0) {

				Scene::g_Geometry.erase(i);
				pMesh->Release();
				delete pMesh;
				return true;
			}
		}
	}

	return false;
}

/**
	Enables all objects within the scene instance to be rendered

	@author Dacre Denny
	@return true if successful otherwise false
*/
bool Scene::Draw() {

	FOREACH(Model*, i, Scene::g_Geometry) {

		Model* pMesh = (*i);
		if(pMesh) {

			pMesh->Draw();
		}
	}

	return true;
}

/**
	Enables all objects within the scene instance to be updated

	@author Dacre Denny
	@return true if successful otherwise false
*/
bool Scene::Update() {

	FOREACH(Model*, i, Scene::g_Geometry) {

		Model* pMesh = (*i);
		if(pMesh) {

			//pMesh->U();
		}
	}

	return true;
}

/**
	Writes the scene's current state to a file on disk for subsequent recall

	@author Dacre Denny
	@param filename filename to write the data to
	@return true if successful and parameters are valid otherwise false
*/
bool Scene::WriteToDisk(std::string& filename) {

	FILE* pFile = NULL;
	try {

		filename.append(".lvs");
		pFile = fopen(filename.c_str(), "wb");
		if(!pFile)
			throw tException("Failed to open file: %s", filename.c_str());

		//Write scene info

		//Write objects
		fprintf(pFile, "%u", Scene::g_Geometry.size());
		FOREACH(Model*, i, Scene::g_Geometry) {

			Model* pMesh = (*i);
			
			tCharBuffer modelID;
			pMesh->GetID(&modelID);
			fwrite(&modelID, sizeof(tCharBuffer), 1, pFile);
			if(!Scene::WriteToDisk(pMesh, std::string(modelID.Buffer))){
#pragma message "Log error"
			}
		}

		fclose(pFile);
		return true;

	}catch(tException e) {


		std::string message;
		tException::GetMessage(&e, message);

		if(pFile)
			fclose(pFile);
		return false;
	}
}

/**
	Reads a scene's previous state from the specified file on disk to subsitutue the current for scene state

	@author Dacre Denny
	@param filename filename to read the data from
	@return true if successful and parameters are valid otherwise false
*/
bool Scene::ReadFromDisk(const std::string& filename) {

	FILE* pFile = NULL;
	try {

		std::string filenameFormat = filename + ".lvs";
		pFile = fopen(filenameFormat.c_str(), "rb");
		if(!pFile)
			throw tException("Failed to open file: %s", filenameFormat.c_str());

		//Read scene info

		//Read objects
		uint objectCount = 0;
		fread(&objectCount, sizeof(uint), 1, pFile);
		
		for(uint i = 0; i < objectCount; i++) {

			tCharBuffer objectName;
			fread(&objectName, sizeof(tCharBuffer), 1, pFile);

			Model* pObject = NULL;
			if(!Scene::ReadFromDisk(std::string(objectName.Buffer), &pObject)) {
#pragma message "Log error"
			}
			else {

				Scene::InsertGeometry(pObject);
			}
		}

		fclose(pFile);
		return true;

	}catch(tException e) {


		std::string message;
		tException::GetMessage(&e, message);

		if(pFile)
			fclose(pFile);
		return false;
	}
}

/**
	Writes a scene object to a file on disk for subsequent recall

	@author Dacre Denny
	@param pMesh pointer to object data source
	@param filename filename to write the object data to
	@return true if successful and parameters are valid otherwise false
*/
bool Scene::WriteToDisk(Model* pMesh, std::string& filename) {

	FILE* pFile = NULL;
	try {

		filename.append(".lvo");
		pFile = fopen(filename.c_str(), "wb");
		if(!pFile)
			throw tException("Failed to open file: %s", filename.c_str());
				
		/*
		Write shader related data
		*/
		//tCharBuffer shaderFilename;
		//ZeroMemory(&shaderFilename, sizeof(tCharBuffer));
		//memcpy(&shaderFilename, pMesh->m_Shader->filename.c_str(), min(pMesh->m_Shader->filename.length(), sizeof(tCharBuffer)));
		//fwrite(&pMesh->m_Shader->Attributes, sizeof(uint), 1, pFile);
		//fwrite(&pMesh->m_Shader->VertexFormat, sizeof(uint), 1, pFile);
		//fwrite(&shaderFilename, sizeof(tCharBuffer), 1, pFile);
		//		
		///*
		//Write misc data
		//*/
		//tMatrix4 World;
		//if(!pMesh->GetTransform(&World))
		//	throw tException();
		//fwrite(&World, sizeof(tMatrix4), 1, pFile);
		//
		//QuerysStructs::tDeformationHeader deformationHeader;
		//if(!pMesh->GetInfoDeformation(&deformationHeader))
		//	throw tException();
		//fwrite(&deformationHeader, sizeof(QuerysStructs::tDeformationHeader), 1, pFile);

		//QuerysStructs::tGeometryHeader geometryHeader;
		//QuerysStructs::tGeometryPartHeader* pPartHeaders = NULL;
		//byte** pPartTriangles = NULL;
		//uint partCount = 0;
		//if(!pMesh->GetInfoGeometry(&geometryHeader, pPartHeaders, pPartTriangles, &partCount))
		//	throw tException();
		////fwrite(&pMesh->m_deformationScale, sizeof(float), 1, pFile);
		////fwrite(&pMesh->m_deformation, sizeof(bool), 1, pFile);
		//		
		///*
		//Write material data
		//*/
		//if(pMesh->m_hDecalMaterial) {

		//	if(!Materials::WriteToDisk(std::string(filename + "_decal"), pMesh->m_hDecalMaterial))
		//		throw tException("Failed to write material file for: %s", std::string(filename + "_normal").c_str());

		//	fwrite("1", sizeof(char), 1, pFile);
		//}

		//if(pMesh->m_hNormalMaterial) {

		//	if(!Materials::WriteToDisk(std::string(filename + "_normal"), pMesh->m_hNormalMaterial))
		//		throw tException("Failed to write material file for: %s", std::string(filename + "_normal").c_str());
		//}

		//fwrite(pMesh->m_hDecalMaterial ? "1" : "0", sizeof(char), 1, pFile);
		//fwrite(pMesh->m_hNormalMaterial ? "1" : "0", sizeof(char), 1, pFile);
		//			
		///*
		//Write geometric data
		//*/			
		//fwrite(&pMesh->m_TriangleCount, sizeof(uint), 1, pFile);
		//fwrite(pMesh->m_pTriangleData, sizeof(byte), 
		//	pMesh->m_TriangleCount * GetVertexFormatSize(pMesh->m_Shader->VertexFormat) * 3,
		//	pFile);

		fclose(pFile);
		return true;

	}catch(tException e) {


		std::string message;
		tException::GetMessage(&e, message);

		if(pFile)
			fclose(pFile);
		return false;
	}
}

/**
	Reads a scene object from a file on disk

	@author Dacre Denny
	@param pMesh pointer to address where the read object data should be stored
	@param filename filename to read the object data from
	@return true if successful and parameters are valid otherwise false
*/
bool Scene::ReadFromDisk(const std::string& filename, Model** pOut) {

	FILE* pFile = NULL;
	//byte* pTriangleData = NULL;
	Model* pObject = NULL;
	//Render::tShader* pShader = NULL;

	//HMATERIAL hDecal = NULL;
	//HMATERIAL hNormal = NULL;

	byte* pGeometryData = NULL;
	byte* pParameterData = NULL;

	try {

		if(!pOut)
			throw tException();

		std::string filenameFormat = filename + ".lvs";
		pFile = fopen(filenameFormat.c_str(), "rb");
		if(!pFile)
			throw tException("Failed to open file: %s", filenameFormat.c_str());
				
		/*
		Read geometry related data
		*/
		HeaderStructs::tGeometryHeader geometryHeader;
		fread(&geometryHeader, sizeof(HeaderStructs::tGeometryHeader), 1, pFile);

		uint vertexSize = GetVertexFormatSize(geometryHeader.vertexFormat);
		uint partCount = geometryHeader.partCount;
		
		if(partCount == 0 || vertexSize == 0)
			throw tException("Invalid geometry data.");

		for(uint i = 0, allocOffset = 0; i < partCount; i++) {

			HeaderStructs::tGeometryPartHeader partHeader;
			fread(&partHeader, sizeof(HeaderStructs::tGeometryPartHeader), 1, pFile);

			if(partHeader.triangleCount == 0)
				throw tException();

			uint dataSize = (partHeader.triangleCount * vertexSize * 3);
			uint partSize = dataSize + sizeof(HeaderStructs::tGeometryPartHeader);
			pGeometryData = (byte*)realloc(pGeometryData, allocOffset + partSize);

			memcpy(pGeometryData + allocOffset, &partHeader, sizeof(HeaderStructs::tGeometryPartHeader));
			fread(pGeometryData + allocOffset + sizeof(HeaderStructs::tGeometryPartHeader), dataSize, 1, pFile);

			allocOffset += partSize;
		}
		
		pObject->UpdateGeometry(&geometryHeader,  pGeometryData);
		free(pGeometryData);
		pGeometryData = NULL;

		/*
		Read position
		*/
		tMatrix4 world;
		fread(&world, sizeof(tMatrix4), 1, pFile);
		pObject->UpdateTransform(&world);
				
		/*
		Read deformation
		*/
		HeaderStructs::tDeformationHeader deformationHeader;
		deformationHeader.proceduralFormat = 0;
		fread(&deformationHeader.scale, sizeof(float), 1, pFile);
		fread(&deformationHeader.remove, sizeof(bool), 1, pFile);
		memcpy(&deformationHeader.target, filename.c_str(), min(sizeof(tCharBuffer), filename.length()));
//	pObject->UpdateDeformation(&deformationHeader); 

		/*
		Read material data
		*/
		HeaderStructs::tParametersHeader parametersHeader;
		fread(&parametersHeader, sizeof(HeaderStructs::tParametersHeader), 1, pFile);

		for(uint i = 0, allocOffset = 0; i < partCount; i++) {

			HeaderStructs::tParametersPartHeader partHeader;
			fread(&partHeader, sizeof(HeaderStructs::tParametersPartHeader), 1, pFile);

			uint partSize = sizeof(HeaderStructs::tParametersPartHeader);
			pParameterData = (byte*)realloc(pParameterData, allocOffset + partSize);

			memcpy(pParameterData + allocOffset, &partHeader, sizeof(HeaderStructs::tGeometryPartHeader));
			fread(pParameterData + allocOffset + sizeof(HeaderStructs::tGeometryPartHeader), partSize, 1, pFile);

			allocOffset += partSize;
		}

		pObject->UpdateParameters(&parametersHeader);
		free(pParameterData);
		pParameterData = NULL;

		if(pFile)
			fclose(pFile);

		return true;
	}
	catch(tException e) {

		std::string message;
		tException::GetMessage(&e, message);

		if(pObject) {

			Scene::RemoveGeometry(pObject);
		}
			
		if(pGeometryData)
			free(pGeometryData);
		
		if(pParameterData)
			free(pParameterData);

		if(pFile)
			fclose(pFile);

		return false;
	}
}

/**
	Enables a light to be inserted into the scene. The function
	fails if the parameter is invalid

	@author Dacre Denny
	@param pointer to the light object to be inserted
	@return true if successful and parameter is valid otherwise false
*/
bool Scene::InsertLight(tLight *pIn) {

	if(!pIn)
		return false;

	FOREACH(tLight*, i, Scene::g_Light)
		if(CharBuffer_Equals(&pIn->m_Name, (*i)->m_Name.Buffer)) {

			delete (*i);
			(*i) = pIn;
			return true;
		}

	Scene::g_Light.push_back(pIn);
	return true;
}

/**
	Enables a light to be removed from the scene. The function
	fails if the parameter is invalid. 

	@author Dacre Denny
	@param pointer to the geometry object to be removed
	@return true if successful and parameter is valid otherwise false
*/
bool Scene::RemoveLight(tLight *pIn) {

	if(!pIn)
		return false;
	
	FOREACH(tLight*, i, Scene::g_Light)
		if(CharBuffer_Equals(&pIn->m_Name, (*i)->m_Name.Buffer)) {

			Scene::g_Light.erase(i);
			delete (*i);
			return true;
		}

	return true;
}

/**
	Returns the light object repsective of the specified name. The function
	fails if either of the parameters are invalid

	@author Dacre Denny
	@param pointer to address of resulting object if it is found
	@param pointer to identification string/object
	@return true if successful and parameters are valid otherwise false
*/
bool Scene::GetLight(tLight **pOut, tCharBuffer *pName) {

	if(!pName || !pOut)
		return false;

	FOREACH(tLight*, i, Scene::g_Light) {

		tLight* pLight = (*i);
		if(pLight) {

			if(strncmp(pName->Buffer, pLight->m_Name.Buffer, sizeof(pLight->m_Name.Buffer)) == 0) {

				*pOut = pLight;
				return true;
			}
		}
	}

	return false;
}

/**
	Enables the client to apply the light parameters of the scene to the specified shader

	@author Dacre Denny
	@param pShader shader object to apply scene light parameters
	@return true if successful and parameters are valid otherwise false
*/
bool Scene::ApplyLights(const Render::tShader* pShader) {

	if(pShader) {

		uint n = 0, m = 0;
		FOREACH(tLight*, i, Scene::g_Light)
			(*i)->ApplyToShader(pShader, (*i)->m_Type == 0 ? n++ : m++);

		return true;
	}

	return false;
}